package modeler;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import javax.swing.Icon;
import javax.vecmath.Point3f;
import javax.vecmath.Vector2f;
import javax.vecmath.Vector3f;

import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.glu.*;

/**
 * Base class of all cameras
 * @author ags
 */
@SuppressWarnings("serial")
public abstract class Camera extends SceneNode {

  //All the camera parameters
  protected float aspect = 1;
  protected float near;
  protected float far;
  protected final Point3f eye = new Point3f();
  protected final Point3f target = new Point3f();
  protected final Vector3f up = new Vector3f();
  protected final Vector3f right = new Vector3f();
  private GLU glu;

  //Icon to use in the tree display
  protected transient Icon icon = null;

  /**
   * Required by IO
   *
   */
  public Camera() {} 
  
  /**
   * Creates a camera from input parameters
   * @param newEye
   * @param newTarget
   * @param newUp
   * @param newNear
   * @param newFar
   * @param newName
   */
  public Camera(Point3f newEye, Point3f newTarget, Vector3f newUp, float newNear, float newFar, String newName) {

    up.set(newUp);
    eye.set(newEye);
    target.set(newTarget);
    near = newNear;
    far = newFar;

    name = newName;
    
    glu = new GLU();
  }

  /**
   * Creates the camera from the current camera parameters
   */
  public void updateFrame() {

    Vector3f negGaze = new Vector3f(eye);
    negGaze.sub(target);
    negGaze.normalize();

    up.normalize();
    right.cross(up, negGaze);
    right.normalize();
    up.cross(negGaze, right);

  }

  /**
   * Return the eye point
   * @return
   */
  public Point3f getEye() {

    return eye;
  }

  /**
   * Return the target point
   * @return
   */
  public Point3f getTarget() {

    return target;
  }

  /**
   * Return the up vector
   * @return
   */
  public Vector3f getUp() {

    return up;
  }

  /**
   * Return the right vector
   * @return
   */
  public Vector3f getRight() {

    return right;
  }

  /**
   * Returns the height of this camera's image
   * @return
   */
  public abstract float getHeight();
  
  /**
   * Set the aspect ratio
   * @param d
   */
  public void setAspect(float d) {

    aspect = d;
  }

  /**
   * Move the camera eye and target by the given vector
   * @param delta
   */
  public void translate(Vector3f delta) {

    eye.add(delta);
    target.add(delta);
  }

  /**
   * Convert a 2D mouse delta into a 3D translation of the camera
   * @param delta
   * @param output
   */
  public abstract void convertMotion(Vector2f delta, Vector3f output);

  /**
   * Zoom the camera a distancce d
   * @param d
   */
  public abstract void zoom(float d);

  /**
   * Flips the camera
   */
  public void flip() {
    
    Vector3f offset = new Vector3f();
    offset.sub(eye, target);
    offset.negate();
    eye.add(target, offset);
    
  }
  
  /**
   * Translate the camera in response to the given mouseDelta
   * @param mouseDelta
   */
  public void track(Vector2f mouseDelta) {
    
    Vector3f temp = new Vector3f();
    convertMotion(mouseDelta, temp);

    eye.add(temp);
    target.add(temp);
    
  }

  /**
   * Set the OpenGL project matrix for this camera
   * @param d
   */
  public abstract void doProjection(GLAutoDrawable d);

  /**
   * Set the model view matrix for this camera
   * @param d
   */
  public void doModelview(GLAutoDrawable d) {
    glu.gluLookAt(eye.x, eye.y, eye.z, target.x, target.y, target.z, up.x, up.y, up.z);
  }
  
  /**
   * @see modeler.SceneNode#readData(java.io.ObjectInputStream)
   */
  protected void readData(ObjectInputStream in) throws IOException {

    aspect = in.readFloat();
    near = in.readFloat();
    far = in.readFloat();
    eye.x = in.readFloat();
    eye.y = in.readFloat();
    eye.z = in.readFloat();
    target.x = in.readFloat();
    target.y = in.readFloat();
    target.z = in.readFloat();
    up.x = in.readFloat();
    up.y = in.readFloat();
    up.z = in.readFloat();
    right.x = in.readFloat();
    right.y = in.readFloat();
    right.z = in.readFloat();
    
  }
  

  /**
   * @see modeler.SceneNode#saveData(java.io.ObjectOutputStream)
   */
  protected void saveData(ObjectOutputStream out) throws IOException {

    out.writeFloat(aspect);
    out.writeFloat(near);
    out.writeFloat(far);
    out.writeFloat(eye.x);
    out.writeFloat(eye.y);
    out.writeFloat(eye.z);
    out.writeFloat(target.x);
    out.writeFloat(target.y);
    out.writeFloat(target.z);
    out.writeFloat(up.x);
    out.writeFloat(up.y);
    out.writeFloat(up.z);
    out.writeFloat(right.x);
    out.writeFloat(right.y);
    out.writeFloat(right.z);
    
  }
  

  /**
   * @see javax.swing.tree.TreeNode#getAllowsChildren()
   */
  public boolean getAllowsChildren() {

    return false;

  }

  /**
   * @see javax.swing.tree.TreeNode#isLeaf()
   */
  public boolean isLeaf() {

    return true;

  }

}